package org.andengine.opengl.texture.atlas.bitmap;

import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.opengl.GLES20;
import android.opengl.GLUtils;

import org.andengine.opengl.texture.PixelFormat;
import org.andengine.opengl.texture.TextureManager;
import org.andengine.opengl.texture.TextureOptions;
import org.andengine.opengl.texture.atlas.TextureAtlas;
import org.andengine.opengl.texture.atlas.bitmap.source.EmptyBitmapTextureAtlasSource;
import org.andengine.opengl.texture.atlas.bitmap.source.IBitmapTextureAtlasSource;
import org.andengine.opengl.texture.atlas.source.ITextureAtlasSource;
import org.andengine.opengl.texture.bitmap.BitmapTextureFormat;
import org.andengine.opengl.util.GLState;
import org.andengine.util.exception.NullBitmapException;
import org.andengine.util.math.MathUtils;

import java.util.ArrayList;

/**
 * (c) 2010 Nicolas Gramlich
 * (c) 2011 Zynga Inc.
 *
 * @author Nicolas Gramlich
 * @since 14:55:02 - 08.03.2010
 */
public class BitmapTextureAtlas extends TextureAtlas<IBitmapTextureAtlasSource> {
    // ===========================================================
    // Constants
    // ===========================================================

    // ===========================================================
    // Fields
    // ===========================================================

    private final BitmapTextureFormat mBitmapTextureFormat;

    // ===========================================================
    // Constructors
    // ===========================================================

    /**
     * Uses {@link BitmapTextureFormat#RGBA_8888}.
     */
    public BitmapTextureAtlas(final TextureManager pTextureManager, final int pWidth, final int pHeight) {
        this(pTextureManager, pWidth, pHeight, BitmapTextureFormat.RGBA_8888);
    }

    /**
     * @param pBitmapTextureFormat use {@link BitmapTextureFormat#RGBA_8888} or {@link BitmapTextureFormat#RGBA_4444} for a {@link org.andengine.opengl.texture.atlas.bitmap.BitmapTextureAtlas} with transparency and {@link BitmapTextureFormat#RGB_565} for a {@link org.andengine.opengl.texture.atlas.bitmap.BitmapTextureAtlas} without transparency.
     */
    public BitmapTextureAtlas(final TextureManager pTextureManager, final int pWidth, final int pHeight, final BitmapTextureFormat pBitmapTextureFormat) {
        this(pTextureManager, pWidth, pHeight, pBitmapTextureFormat, TextureOptions.DEFAULT, null);
    }

    /**
     * Uses {@link BitmapTextureFormat#RGBA_8888}.
     *
     * @param pTextureStateListener to be informed when this {@link org.andengine.opengl.texture.atlas.bitmap.BitmapTextureAtlas} is loaded, unloaded or a {@link ITextureAtlasSource} failed to load.
     */
    public BitmapTextureAtlas(final TextureManager pTextureManager, final int pWidth, final int pHeight, final ITextureAtlasStateListener<IBitmapTextureAtlasSource> pTextureAtlasStateListener) {
        this(pTextureManager, pWidth, pHeight, BitmapTextureFormat.RGBA_8888, TextureOptions.DEFAULT, pTextureAtlasStateListener);
    }

    /**
     * @param pBitmapTextureFormat       use {@link BitmapTextureFormat#RGBA_8888} or {@link BitmapTextureFormat#RGBA_4444} for a {@link org.andengine.opengl.texture.atlas.bitmap.BitmapTextureAtlas} with transparency and {@link BitmapTextureFormat#RGB_565} for a {@link org.andengine.opengl.texture.atlas.bitmap.BitmapTextureAtlas} without transparency.
     * @param pTextureAtlasStateListener to be informed when this {@link org.andengine.opengl.texture.atlas.bitmap.BitmapTextureAtlas} is loaded, unloaded or a {@link ITextureAtlasSource} failed to load.
     */
    public BitmapTextureAtlas(final TextureManager pTextureManager, final int pWidth, final int pHeight, final BitmapTextureFormat pBitmapTextureFormat, final ITextureAtlasStateListener<IBitmapTextureAtlasSource> pTextureAtlasStateListener) {
        this(pTextureManager, pWidth, pHeight, pBitmapTextureFormat, TextureOptions.DEFAULT, pTextureAtlasStateListener);
    }

    /**
     * Uses {@link BitmapTextureFormat#RGBA_8888}.
     *
     * @param pTextureOptions the (quality) settings of this {@link org.andengine.opengl.texture.atlas.bitmap.BitmapTextureAtlas}.
     */
    public BitmapTextureAtlas(final TextureManager pTextureManager, final int pWidth, final int pHeight, final TextureOptions pTextureOptions) throws IllegalArgumentException {
        this(pTextureManager, pWidth, pHeight, BitmapTextureFormat.RGBA_8888, pTextureOptions, null);
    }

    /**
     * @param pBitmapTextureFormat use {@link BitmapTextureFormat#RGBA_8888} or {@link BitmapTextureFormat#RGBA_4444} for a {@link org.andengine.opengl.texture.atlas.bitmap.BitmapTextureAtlas} with transparency and {@link BitmapTextureFormat#RGB_565} for a {@link org.andengine.opengl.texture.atlas.bitmap.BitmapTextureAtlas} without transparency.
     * @param pTextureOptions      the (quality) settings of this {@link org.andengine.opengl.texture.atlas.bitmap.BitmapTextureAtlas}.
     */
    public BitmapTextureAtlas(final TextureManager pTextureManager, final int pWidth, final int pHeight, final BitmapTextureFormat pBitmapTextureFormat, final TextureOptions pTextureOptions) throws IllegalArgumentException {
        this(pTextureManager, pWidth, pHeight, pBitmapTextureFormat, pTextureOptions, null);
    }

    /**
     * Uses {@link BitmapTextureFormat#RGBA_8888}.
     *
     * @param pTextureOptions            the (quality) settings of this {@link org.andengine.opengl.texture.atlas.bitmap.BitmapTextureAtlas}.
     * @param pTextureAtlasStateListener to be informed when this {@link org.andengine.opengl.texture.atlas.bitmap.BitmapTextureAtlas} is loaded, unloaded or a {@link ITextureAtlasSource} failed to load.
     */
    public BitmapTextureAtlas(final TextureManager pTextureManager, final int pWidth, final int pHeight, final TextureOptions pTextureOptions, final ITextureAtlasStateListener<IBitmapTextureAtlasSource> pTextureAtlasStateListener) throws IllegalArgumentException {
        this(pTextureManager, pWidth, pHeight, BitmapTextureFormat.RGBA_8888, pTextureOptions, pTextureAtlasStateListener);
    }

    /**
     * @param pBitmapTextureFormat       use {@link BitmapTextureFormat#RGBA_8888} or {@link BitmapTextureFormat#RGBA_4444} for a {@link org.andengine.opengl.texture.atlas.bitmap.BitmapTextureAtlas} with transparency and {@link BitmapTextureFormat#RGB_565} for a {@link org.andengine.opengl.texture.atlas.bitmap.BitmapTextureAtlas} without transparency.
     * @param pTextureOptions            the (quality) settings of this {@link org.andengine.opengl.texture.atlas.bitmap.BitmapTextureAtlas}.
     * @param pTextureAtlasStateListener to be informed when this {@link org.andengine.opengl.texture.atlas.bitmap.BitmapTextureAtlas} is loaded, unloaded or a {@link ITextureAtlasSource} failed to load.
     */
    public BitmapTextureAtlas(final TextureManager pTextureManager, final int pWidth, final int pHeight, final BitmapTextureFormat pBitmapTextureFormat, final TextureOptions pTextureOptions, final ITextureAtlasStateListener<IBitmapTextureAtlasSource> pTextureAtlasStateListener) throws IllegalArgumentException {
        super(pTextureManager, pWidth, pHeight, pBitmapTextureFormat.getPixelFormat(), pTextureOptions, pTextureAtlasStateListener);

        this.mBitmapTextureFormat = pBitmapTextureFormat;
    }

    // ===========================================================
    // Getter & Setter
    // ===========================================================

    public BitmapTextureFormat getBitmapTextureFormat() {
        return this.mBitmapTextureFormat;
    }

    // ===========================================================
    // Methods for/from SuperClass/Interfaces
    // ===========================================================

    @Override
    public void addEmptyTextureAtlasSource(final int pTextureX, final int pTextureY, final int pWidth, final int pHeight) {
        this.addTextureAtlasSource(new EmptyBitmapTextureAtlasSource(pWidth, pHeight), pTextureX, pTextureY);
    }

    // ===========================================================
    // Methods
    // ===========================================================

    @Override
    protected void writeTextureToHardware(final GLState pGLState) {
        final PixelFormat pixelFormat = this.mBitmapTextureFormat.getPixelFormat();
        final int glInternalFormat = pixelFormat.getGLInternalFormat();
        final int glFormat = pixelFormat.getGLFormat();
        final int glType = pixelFormat.getGLType();

        GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, glInternalFormat, this.mWidth, this.mHeight, 0, glFormat, glType, null);

        final boolean preMultipyAlpha = this.mTextureOptions.mPreMultiplyAlpha;
        /* Non alpha premultiplied bitmaps are loaded with ARGB_8888 and converted down manually. */
        final Config bitmapConfig = (preMultipyAlpha) ? this.mBitmapTextureFormat.getBitmapConfig() : Config.ARGB_8888;

        final ArrayList<IBitmapTextureAtlasSource> textureSources = this.mTextureAtlasSources;
        final int textureSourceCount = textureSources.size();

        final ITextureAtlasStateListener<IBitmapTextureAtlasSource> textureStateListener = this.getTextureAtlasStateListener();
        for (int i = 0; i < textureSourceCount; i++) {
            final IBitmapTextureAtlasSource bitmapTextureAtlasSource = textureSources.get(i);
            try {
                final Bitmap bitmap = bitmapTextureAtlasSource.onLoadBitmap(bitmapConfig);
                if (bitmap == null) {
                    throw new NullBitmapException("Caused by: " + bitmapTextureAtlasSource.getClass().toString() + " --> " + bitmapTextureAtlasSource.toString() + " returned a null Bitmap.");
                }

                final boolean useDefaultAlignment = MathUtils.isPowerOfTwo(bitmap.getWidth()) && MathUtils.isPowerOfTwo(bitmap.getHeight()) && pixelFormat == PixelFormat.RGBA_8888;
                if (!useDefaultAlignment) {
                    /* Adjust unpack alignment. */
                    GLES20.glPixelStorei(GLES20.GL_UNPACK_ALIGNMENT, 1);
                }

                if (preMultipyAlpha) {
                    GLUtils.texSubImage2D(GLES20.GL_TEXTURE_2D, 0, bitmapTextureAtlasSource.getTextureX(), bitmapTextureAtlasSource.getTextureY(), bitmap, glFormat, glType);
                } else {
                    pGLState.glTexSubImage2D(GLES20.GL_TEXTURE_2D, 0, bitmapTextureAtlasSource.getTextureX(), bitmapTextureAtlasSource.getTextureY(), bitmap, this.mPixelFormat);
                }

                if (!useDefaultAlignment) {
                    /* Restore default unpack alignment. */
                    GLES20.glPixelStorei(GLES20.GL_UNPACK_ALIGNMENT, GLState.GL_UNPACK_ALIGNMENT_DEFAULT);
                }

                bitmap.recycle();

                if (textureStateListener != null) {
                    textureStateListener.onTextureAtlasSourceLoaded(this, bitmapTextureAtlasSource);
                }
            } catch (final NullBitmapException e) {
                if (textureStateListener != null) {
                    textureStateListener.onTextureAtlasSourceLoadExeption(this, bitmapTextureAtlasSource, e);
                } else {
                    throw e;
                }
            }
        }
    }

    // ===========================================================
    // Inner and Anonymous Classes
    // ===========================================================
}
